#include "spk_camera.h"
#include "config.h"
#include "video.h"
#include "video_mpp.h"

#define LOG_TAG "video_mpp"
#define LOG_LVL LOG_LVL_INFO
#include "spk_ulog.h"

cls_video_mpp::cls_video_mpp(cls_video *p_video)
{
    video = p_video;

    memset(&mpp_enc_data, 0, sizeof(mpp_enc_data));
    mpp_enc_data.width = video->img_org.width;
    mpp_enc_data.height = video->img_org.height;
    mpp_enc_data.frame_size = video->img_org.size;
    mpp_enc_data.hor_stride = MPP_ALIGN(mpp_enc_data.width, 16);
    mpp_enc_data.ver_stride = MPP_ALIGN(mpp_enc_data.height, 16);
    mpp_enc_data.fmt = MPP_FMT_YUV422_YUYV; // MPP_FMT_BGR565;//MPP_FMT_YUV422_YUYV;
    mpp_enc_data.type = MPP_VIDEO_CodingAVC;
    mpp_enc_data.fps = 30;
    mpp_enc_data.gop = 8;
    // mpp_enc_data.bps = mpp_enc_data.width * mpp_enc_data.height / 8 * mpp_enc_data.fps;
    mpp_enc_data.bps = 500 * 1000;
    init_mpp();

#ifdef MPP_OUTPUT_TEST
    mpp_enc_data.fp_output = fopen("./mpp_v4l2.h264", "wb+"); // mpp output test
#endif
}

cls_video_mpp::~cls_video_mpp()
{
    distory_mpp();
}

void cls_video_mpp::encode_image(ctx_image_data *image_data)
{
    MPP_RET ret = MPP_OK;
    MppFrame frame = NULL;
    MppPacket packet = NULL;
    void *buf = mpp_buffer_get_ptr(mpp_enc_data.frm_buf);

    // TODO: improve performance here?
    memcpy(buf, image_data->data, image_data->size);

    ret = mpp_frame_init(&frame);
    if (ret)
    {
        printf("mpp_frame_init failed\n");
        return;
    }

    mpp_frame_set_width(frame, mpp_enc_data.width);
    mpp_frame_set_height(frame, mpp_enc_data.height);
    mpp_frame_set_hor_stride(frame, mpp_enc_data.hor_stride);
    mpp_frame_set_ver_stride(frame, mpp_enc_data.ver_stride);
    mpp_frame_set_fmt(frame, mpp_enc_data.fmt);
    mpp_frame_set_buffer(frame, mpp_enc_data.frm_buf);
    mpp_frame_set_eos(frame, mpp_enc_data.frm_eos);

    ret = mpp_enc_data.mpi->encode_put_frame(mpp_enc_data.ctx, frame);
    if (ret)
    {
        printf("mpp encode put frame failed\n");
        return;
    }

    ret = mpp_enc_data.mpi->encode_get_packet(mpp_enc_data.ctx, &packet);
    if (ret)
    {
        printf("mpp encode get packet failed\n");
        return;
    }

    if (packet)
    {
        // write packet to file here
        void *ptr = mpp_packet_get_pos(packet);
        size_t len = mpp_packet_get_length(packet);

#ifdef MPP_OUTPUT_TEST
        // mpp output test
        if (mpp_enc_data.fp_output)
            fwrite(ptr, 1, len, mpp_enc_data.fp_output);
#endif

        mpp_enc_data.pkt_eos = mpp_packet_get_eos(packet);

        mpp_packet_deinit(&packet);

        // printf("encoded frame %d size %d\n", mpp_enc_data.frame_count, len);
        mpp_enc_data.stream_size += len;
        mpp_enc_data.frame_count++;

        u16 msg_head = len;

        char *msg = (char *)malloc(len + 2);
        msg[0] = msg_head & 0xFF; /*长度*/
        msg[1] = (msg_head >> 8) & 0xFF;
        // memcpy(msg, &msg_head, 2);
        memcpy(msg + 2, ptr, len);

        unsigned char nalut = 0;
        unsigned char *pData = (unsigned char *)ptr;
        if (pData[3] == 1)
            nalut = pData[4];
        else if (pData[2] == 1)
            nalut = pData[3];
        int IFrame = ((nalut & 0x1F) == 7);
        if (IFrame)
        {
            LOG_D("This Frame is I Frame");
        }

        /***
         *_______________#########_______________________
         *______________############_____________________
         *______________#############____________________
         *_____________##__###########___________________
         *____________###__######_#####__________________
         *____________###_#######___####_________________
         *___________###__##########_####________________
         *__________####__###########_####_______________
         *________#####___###########__#####_____________
         *_______######___###_########___#####___________
         *_______#####___###___########___######_________
         *______######___###__###########___######_______
         *_____######___####_##############__######______
         *____#######__#####################_#######_____
         *____#######__##############################____
         *___#######__######_#################_#######___
         *___#######__######_######_#########___######___
         *___#######____##__######___######_____######___
         *___#######________######____#####_____#####____
         *____######________#####_____#####_____####_____
         *_____#####________####______#####_____###______
         *______#####______;###________###______#________
         *________##_______####________####______________
         */
        send_msg_queue(video->app->video_queue, msg, len + 2, 0);
        // LOG_D("Frame len = %d", len);

        free(msg);
    }

    if (mpp_enc_data.num_frames && mpp_enc_data.frame_count >= mpp_enc_data.num_frames)
    {
        printf("encode max %d frames", mpp_enc_data.frame_count);
        return;
    }

    if (mpp_enc_data.frm_eos && mpp_enc_data.pkt_eos)
        return;

    return;
}

void cls_video_mpp::init_mpp()
{
    MPP_RET ret = MPP_OK;
    MppEncHeaderMode hm = MPP_ENC_HEADER_MODE_EACH_IDR;

    ret = mpp_buffer_get(NULL, &mpp_enc_data.frm_buf, mpp_enc_data.frame_size);
    if (ret)
    {
        printf("failed to get buffer for input frame ret %d\n", ret);
        goto MPP_INIT_OUT;
    }
    // 创建 MPP context 和 MPP api 接口
    ret = mpp_create(&mpp_enc_data.ctx, &mpp_enc_data.mpi);
    if (ret)
    {
        printf("mpp_create failed ret %d\n", ret);
        goto MPP_INIT_OUT;
    }
    /*初始化编码还是解码，以及编解码的格式
    MPP_CTX_DEC ： 解码
    MPP_CTX_ENC ： 编码
    MPP_VIDEO_CodingAVC ： H.264
    MPP_VIDEO_CodingHEVC :  H.265
    MPP_VIDEO_CodingVP8 :  VP8
    MPP_VIDEO_CodingVP9 :  VP9
    MPP_VIDEO_CodingMJPEG : MJPEG*/
    ret = mpp_init(mpp_enc_data.ctx, MPP_CTX_ENC, mpp_enc_data.type);
    if (ret)
    {
        printf("mpp_init failed ret %d\n", ret);
        goto MPP_INIT_OUT;
    }

    // ******************************
    /* RC_CFG */
    // ******************************
    /*设置编码码率、质量、定码率变码率*/
    mpp_enc_data.rc_cfg.change = MPP_ENC_RC_CFG_CHANGE_ALL;
    mpp_enc_data.rc_cfg.rc_mode = MPP_ENC_RC_MODE_VBR;       // cbr means bps cannt change
    mpp_enc_data.rc_cfg.quality = MPP_ENC_RC_QUALITY_MEDIUM; // 表示编码图像质量模式，共分为七档，主要在VBR模式下有生效，因为CBR模式为码率优先，图像质量模式不起作用

    if (mpp_enc_data.rc_cfg.rc_mode == MPP_ENC_RC_MODE_CBR)
    {
        /* constant bitrate has very small bps range of 1/16 bps */
        mpp_enc_data.rc_cfg.bps_target = mpp_enc_data.bps;
        mpp_enc_data.rc_cfg.bps_max = mpp_enc_data.bps * 17 / 16;
        mpp_enc_data.rc_cfg.bps_min = mpp_enc_data.bps * 15 / 16;
    }
    else if (mpp_enc_data.rc_cfg.rc_mode == MPP_ENC_RC_MODE_VBR)
    {
        /* constant bitrate has very small bps range of 1/16 bps */
        mpp_enc_data.rc_cfg.bps_target = mpp_enc_data.bps;

        // // *** mpp-lhj ver ***
        // mpp_enc_data.rc_cfg.bps_max = mpp_enc_data.bps * 17 / 16;
        // mpp_enc_data.rc_cfg.bps_min = mpp_enc_data.bps * 1 / 16;
        // *** mpp-1.0.5 ver ***
        mpp_enc_data.rc_cfg.bps_max = mpp_enc_data.bps * 3 / 2;
        mpp_enc_data.rc_cfg.bps_min = mpp_enc_data.bps * 1 / 2;
    }

    /* fix input / output frame rate */
    mpp_enc_data.rc_cfg.fps_in_flex = 0;
    mpp_enc_data.rc_cfg.fps_in_num = mpp_enc_data.fps;

    // // *** mpp-lhj ver ***
    // mpp_enc_data.rc_cfg.fps_in_denorm = 1;
    // *** mpp-1.0.5 ver ***
    mpp_enc_data.rc_cfg.fps_in_denom = 1;

    mpp_enc_data.rc_cfg.fps_out_flex = 0;
    mpp_enc_data.rc_cfg.fps_out_num = mpp_enc_data.fps;

    // // *** mpp-lhj ver ***
    // mpp_enc_data.rc_cfg.fps_out_denorm = 1;
    // *** mpp-1.0.5 ver ***
    mpp_enc_data.rc_cfg.fps_out_denom = 1;

    mpp_enc_data.rc_cfg.gop = mpp_enc_data.gop;
    mpp_enc_data.rc_cfg.skip_cnt = 0;

    // // *** mpp-lhj ver ***
    // mpp_enc_data.rc_cfg.qp_init = -1;
    // mpp_enc_data.rc_cfg.qp_max = 51;
    // mpp_enc_data.rc_cfg.qp_min = 10;
    // mpp_enc_data.rc_cfg.qp_max_i = 51;
    // mpp_enc_data.rc_cfg.qp_min_i = 10;
    // *** mpp-1.0.5 ver ***
    mpp_enc_data.rc_cfg.qp_init = 28;
    mpp_enc_data.rc_cfg.qp_max = 40;
    mpp_enc_data.rc_cfg.qp_min = 20;
    mpp_enc_data.rc_cfg.qp_max_i = 35;
    mpp_enc_data.rc_cfg.qp_min_i = 22;

    /* drop frame or not when bitrate overflow */
    mpp_enc_data.rc_cfg.drop_mode = MPP_ENC_RC_DROP_FRM_DISABLED;
    mpp_enc_data.rc_cfg.drop_threshold = 20;
    mpp_enc_data.rc_cfg.drop_gap = 1;

    ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_RC_CFG, &mpp_enc_data.rc_cfg);
    if (ret)
    {
        printf("mpi control enc set rc cfg failed ret %d\n", ret);
        goto MPP_INIT_OUT;
    }

    // ******************************
    /* PREP_CFG */
    // ******************************
    /*设置编码参数：宽高、对齐后宽高等参数*/
    mpp_enc_data.prep_cfg.change = MPP_ENC_PREP_CFG_CHANGE_INPUT |
                                   MPP_ENC_PREP_CFG_CHANGE_ROTATION |
                                   MPP_ENC_PREP_CFG_CHANGE_FORMAT;
    mpp_enc_data.prep_cfg.width = mpp_enc_data.width;
    mpp_enc_data.prep_cfg.height = mpp_enc_data.height;
    mpp_enc_data.prep_cfg.hor_stride = mpp_enc_data.hor_stride;
    mpp_enc_data.prep_cfg.ver_stride = mpp_enc_data.ver_stride;
    mpp_enc_data.prep_cfg.format = mpp_enc_data.fmt;
    mpp_enc_data.prep_cfg.rotation = MPP_ENC_ROT_0;
    ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_PREP_CFG, &mpp_enc_data.prep_cfg);
    if (ret)
    {
        printf("mpi control enc set prep cfg failed ret %d\n", ret);
        goto MPP_INIT_OUT;
    }

    // ******************************
    /* CODEC_CFG */
    // ******************************
    /*设置264相关的其他编码参数*/
    mpp_enc_data.codec_cfg.coding = mpp_enc_data.type;
    switch (mpp_enc_data.codec_cfg.coding)
    {
    case MPP_VIDEO_CodingAVC:
    {
        mpp_enc_data.codec_cfg.h264.change = MPP_ENC_H264_CFG_CHANGE_PROFILE |
                                             MPP_ENC_H264_CFG_CHANGE_ENTROPY |
                                             MPP_ENC_H264_CFG_CHANGE_TRANS_8x8;
        /*
         * H.264 profile_idc parameter
         * 66  - Baseline profile
         * 77  - Main profile
         * 100 - High profile
         */
        mpp_enc_data.codec_cfg.h264.profile = 100;
        /*
         * H.264 level_idc parameter
         * 10 / 11 / 12 / 13    - qcif@15fps / cif@7.5fps / cif@15fps / cif@30fps
         * 20 / 21 / 22         - cif@30fps / half-D1@@25fps / D1@12.5fps
         * 30 / 31 / 32         - D1@25fps / 720p@30fps / 720p@60fps
         * 40 / 41 / 42         - 1080p@30fps / 1080p@30fps / 1080p@60fps
         * 50 / 51 / 52         - 4K@30fps
         */
        mpp_enc_data.codec_cfg.h264.level = 31;
        mpp_enc_data.codec_cfg.h264.entropy_coding_mode = 0;
        mpp_enc_data.codec_cfg.h264.entropy_coding_mode_ex = 0;
        mpp_enc_data.codec_cfg.h264.cabac_init_idc = 0;
        mpp_enc_data.codec_cfg.h264.cabac_init_idc_ex = 0;
        mpp_enc_data.codec_cfg.h264.transform8x8_mode = 1;
    }
    break;
    case MPP_VIDEO_CodingMJPEG:
    {
        mpp_enc_data.codec_cfg.jpeg.change = MPP_ENC_JPEG_CFG_CHANGE_QP;
        mpp_enc_data.codec_cfg.jpeg.quant = 10;
    }
    break;
    case MPP_VIDEO_CodingVP8:
    case MPP_VIDEO_CodingHEVC:
    default:
    {
        printf("support encoder coding type %d\n", mpp_enc_data.codec_cfg.coding);
    }
    break;
    }

    ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_CODEC_CFG, &mpp_enc_data.codec_cfg);
    if (ret)
    {
        printf("mpi control enc set codec cfg failed ret %d\n", ret);
        goto MPP_INIT_OUT;
    }

    /* optional */
    mpp_enc_data.sei_mode = MPP_ENC_SEI_MODE_DISABLE; // MPP_ENC_SEI_MODE_ONE_FRAME;//;
    ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_SEI_CFG, &mpp_enc_data.sei_mode);
    if (ret)
    {
        printf("mpi control enc set sei cfg failed ret %d\n", ret);
        goto MPP_INIT_OUT;
    }

    ret = mpp_enc_data.mpi->control(mpp_enc_data.ctx, MPP_ENC_SET_HEADER_MODE, &hm);
    if (ret)
    {
        printf("mpi control enc set header cfg failed ret %d\n", ret);
        goto MPP_INIT_OUT;
    }
    return;

MPP_INIT_OUT:

    if (mpp_enc_data.ctx)
    {
        mpp_destroy(mpp_enc_data.ctx);
        mpp_enc_data.ctx = NULL;
    }

    if (mpp_enc_data.frm_buf)
    {
        mpp_buffer_put(mpp_enc_data.frm_buf);
        mpp_enc_data.frm_buf = NULL;
    }

    printf("init mpp failed!\n");
}
void cls_video_mpp::distory_mpp()
{
    MPP_RET ret = MPP_OK;
    ret = mpp_enc_data.mpi->reset(mpp_enc_data.ctx);
    if (ret)
    {
        printf("mpi->reset failed\n");
    }

    if (mpp_enc_data.ctx)
    {
        mpp_destroy(mpp_enc_data.ctx);
        mpp_enc_data.ctx = NULL;
    }

    if (mpp_enc_data.frm_buf)
    {
        mpp_buffer_put(mpp_enc_data.frm_buf);
        mpp_enc_data.frm_buf = NULL;
    }
}
